class Token(object):
    def __init__(self, type, value):
        # token type: INTEGER, PLUS, MINUS, MUL, DIV, or EOF
        self.type = type
        # token value: non-negative integer value, '+', '-', '*', '/', or None
        self.value = value

    def __str__(self):
        """String representation of the class instance.

        Examples:
            Token(INTEGER, 3)
            Token(PLUS, '+')
            Token(MUL, '*')
        """
        return 'Token({type}, {value})'.format(
            type=self.type,
            value=repr(self.value)
        )

    def __repr__(self):
        return self.__str__()


# Token types
#
# EOF (end-of-file) token is used to indicate that
# there is no more input left for lexical analysis
ASSIGN = 'ASSIGN'
BEGIN = 'BEGIN'
COLON = 'COLON'
COMMA = 'COMMA'
DOT = 'DOT'
END = 'END'
EOF = 'EOF'
FLOAT_DIV = 'FLOAT_DIV'  # forward slash /, result is a float point number
ID = 'ID'
INTEGER_CONST = 'INTEGER_CONST'
INTEGER_DIV = 'INTEGER_DIV'
INTEGER = 'INTEGER'
LPAREN = 'LPAREN'
MINUS = 'MINUS'
MUL = 'MUL'
PLUS = 'PLUS'
PROGRAM = 'PROGRAM'
REAL_CONST = 'REAL_CONST'
REAL = 'REAL'
RPAREN = 'RPAREN'
SEMI = 'SEMI'
VAR = 'VAR'

RESERVED_KEYWORDS = {
    'BEGIN': Token(BEGIN, 'BEGIN'),
    'DIV': Token(INTEGER_DIV, 'DIV'),
    'END': Token(END, 'END'),
    'INTEGER': Token(INTEGER, 'INTEGER'),
    'PROGRAM': Token(PROGRAM, 'PROGRAM'),
    'REAL': Token(REAL, 'REAL'),
    'VAR': Token(VAR, 'VAR'),
}


class Lexer(object):
    def __init__(self, text):
        # client string input, e.g. "3 * 5", "12 / 3 * 4", etc
        self.text = text
        # self.pos is an index into self.text
        self.pos = 0
        self.current_char = self.text[self.pos]

    def error(self):
        raise Exception('Invalid character: ' + self.current_char)

    def peek(self, l=1):
        """Fetch character but without incrementing the self.pos variable"""
        pos = self.pos + l
        if pos < len(self.text):
            return self.text[pos]
        else:
            return None

    def advance(self):
        """Advance the `pos` pointer and set the `current_char` variable."""
        self.pos += 1
        if self.pos < len(self.text):
            self.current_char = self.text[self.pos]
        else:
            self.current_char = None

    def skip_whitespace(self):
        while self.current_char is not None and self.current_char.isspace():
            self.advance()

    def skip_comment(self):
        while self.current_char != "}":
            self.advance()
        self.advance()  # the closing curly brace

    def _id(self):
        """Handle identifiers and reserved keywords"""
        result = ''
        while self.current_char is not None and self.current_char.isalnum():
            result += self.current_char
            self.advance()

        token = RESERVED_KEYWORDS.get(result, Token(ID, result))
        return token

    def number(self):
        """Return a (multidigit) integer consumed from the input."""
        result = ''
        while self.current_char is not None and self.current_char.isdigit():
            result += self.current_char
            self.advance()

        if self.current_char == '.':
            result += self.current_char
            self.advance()
            while (
                    self.current_char is not None
                    and self.current_char.isdigit()
            ):
                result += self.current_char
                self.advance()

            token = Token(REAL_CONST, float(result))
        else:
            token = Token(INTEGER_CONST, int(result))

        return token

    def get_next_token(self):
        """Lexical analyzer (also known as scanner or tokenizer)

        This method is responsible for breaking a sentence
        apart into tokens. One token at a time.
        """
        while self.current_char is not None:

            if self.current_char.isspace():
                self.skip_whitespace()
                continue

            if self.current_char == '{':
                self.skip_comment()
                continue

            if self.current_char.isdigit():
                return self.number()

            if self.current_char.isalpha():
                return self._id()

            if self.current_char == ':' and self.peek() == '=':
                self.advance()
                self.advance()
                return Token(ASSIGN, ':=')

            if self.current_char == ':':
                self.advance()
                return Token(COLON, ':')

            if self.current_char == ',':
                self.advance()
                return Token(COMMA, ',')

            if self.current_char == ';':
                self.advance()
                return Token(SEMI, ';')

            if self.current_char == '.':
                self.advance()
                return Token(DOT, '.')

            if self.current_char == '+':
                self.advance()
                return Token(PLUS, '+')

            if self.current_char == '-':
                self.advance()
                return Token(MINUS, '-')

            if self.current_char == '*':
                self.advance()
                return Token(MUL, '*')

            if self.current_char == '/':
                self.advance()
                return Token(FLOAT_DIV, '/')

            if self.current_char == '(':
                self.advance()
                return Token(LPAREN, '(')

            if self.current_char == ')':
                self.advance()
                return Token(RPAREN, ')')

            self.error()

        return Token(EOF, None)


class AST(object):
    pass


class UnaryOp(AST):
    def __init__(self, op, expr):
        self.token = op
        self.op = self.token
        self.expr = expr


class BinOp(AST):
    def __init__(self, left, op, right):
        self.token = op
        self.op = self.token
        self.left = left
        self.right = right


class Num(AST):
    def __init__(self, factor):
        self.token = factor
        self.value = self.token.value


class Program(AST):
    def __init__(self, name, block):
        self.name = name
        self.block = block


class Block(AST):
    def __init__(self, declarations, compound_statement):
        self.declarations = declarations
        self.compound_statement = compound_statement


class VarDecl(AST):
    def __init__(self, var_node, type_node):
        self.var_node = var_node
        self.type_node = type_node


class Type(AST):
    def __init__(self, token):
        self.token = token
        self.value = token.value


class Compound(AST):
    def __init__(self):
        self.children = []


class Assign(AST):
    def __init__(self, left, op, right):
        self.token = op
        self.op = self.token
        self.left = left
        self.right = right


class Var(AST):
    """The Var node is constructed out of ID token."""
    def __init__(self, token):
        self.token = token
        self.value = token.value


class NoOp(AST):
    pass


class Parser(object):
    def __init__(self, lexer):
        self.lexer = lexer
        self.current_token = lexer.get_next_token()

    def error(self):
        raise Exception('Invalid token: ' + repr(self.current_token))

    def eat(self, type):
        # print("Eat Token: " + repr(self.current_token))
        if self.current_token.type == type:
            self.current_token = self.lexer.get_next_token()
        else:
            self.error()

    def parse(self):
        root = self.program()
        if self.current_token.type != EOF:
            self.error()

        return root

    def block(self):
        """block : declarations compound_statement"""
        declarations = self.declarations()
        compound_statement = self.compound_statement()

        return Block(declarations, compound_statement)

    def declarations(self):
        """declarations : VAR (variable_declaration SEMI)+
                        | empty
        """
        declarations = []
        if self.current_token.type == VAR:
            self.eat(VAR)
            while self.current_token.type == ID:
                var_decl = self.variable_declaration()
                declarations.extend(var_decl)
                self.eat(SEMI)

        return declarations

    def variable_declaration(self):
        """variable_declaration : ID (COMMA ID)* COLON type_spec"""
        declarations = [Var(self.current_token)]
        self.eat(ID)

        while self.current_token.type == COMMA:
            self.eat(COMMA)
            declarations.append(Var(self.current_token))
            self.eat(ID)

        self.eat(COLON)
        type_spec = self.spec()

        declarations = [
            VarDecl(var_node, type_spec)
            for var_node in declarations
        ]

        return declarations

    def spec(self):
        """type_spec : INTEGER
                     | REAL
        """
        token = self.current_token
        if token.type == INTEGER:
            self.eat(INTEGER)
        elif token.type == REAL:
            self.eat(REAL)

        return Type(token)

    def program(self):
        """program : PROGRAM variable SEMI block DOT"""
        self.eat(PROGRAM)
        prog_name = self.variable().value
        self.eat(SEMI)
        prog_block = self.block()
        self.eat(DOT)

        return Program(prog_name, prog_block)

    def compound_statement(self):
        """
        compound_statement: BEGIN statement_list END
        """
        self.eat(BEGIN)
        node_list = self.statement_list()
        self.eat(END)

        compound_node = Compound()
        for node in node_list:
            compound_node.children.append(node)

        return compound_node

    def statement_list(self):
        """
        statement_list : statement
                       | statement SEMI statement_list
        """
        node_list = []
        node_list.append(self.statement())
        while self.current_token.type == SEMI:
            self.eat(SEMI)
            node_list.append(self.statement())

        if self.current_token.type == ID:
            self.error()

        return node_list

    def statement(self):
        """
        statement : compound_statement
                  | assignment_statement
                  | empty
        """
        if self.current_token.type == BEGIN:
            node = self.compound_statement()
        elif self.current_token.type == ID:
            node = self.assign_statement()
        else:
            node = self.empty()

        return node

    def assign_statement(self):
        """
        assignment_statement : variable ASSIGN expr
        """
        variable = self.variable()
        assign = self.current_token
        self.eat(ASSIGN)
        expr = self.expr()

        return Assign(variable, assign, expr)

    def empty(self):
        """An empty production"""
        return NoOp()

    def expr(self):
        """term : term ((PLUS | MINUS) term)*"""
        node = self.term()
        while True:
            op = self.current_token
            if op.type == PLUS:
                self.eat(PLUS)
            elif op.type == MINUS:
                self.eat(MINUS)
            else:
                break

            node = BinOp(left=node, op=op, right=self.term())

        return node

    def term(self):
        """term : factor ((MUL | INTEGER_DIV | FLOAT_DIV) factor)*"""
        node = self.factor()
        while True:
            op = self.current_token
            if op.type == MUL:
                self.eat(MUL)
            elif op.type == INTEGER_DIV:
                self.eat(INTEGER_DIV)
            elif op.type == FLOAT_DIV:
                self.eat(FLOAT_DIV)
            else:
                break

            node = BinOp(left=node, op=op, right=self.factor())

        return node

    def factor(self):
        """factor : PLUS factor
                  | MINUS factor
                  | INTEGER_CONST
                  | REAL_CONST
                  | LPAREN expr RPAREN
                  | variable
        """
        token = self.current_token
        if token.type == PLUS:
            self.eat(PLUS)
            node = UnaryOp(token, self.factor())
        elif token.type == MINUS:
            self.eat(MINUS)
            node = UnaryOp(token, self.factor())
        elif token.type == LPAREN:
            self.eat(LPAREN)
            node = self.expr()
            self.eat(RPAREN)
        elif token.type == REAL_CONST:
            self.eat(REAL_CONST)
            node = Num(token)
        elif token.type == INTEGER_CONST:
            self.eat(INTEGER_CONST)
            node = Num(token)
        else:
            node = self.variable()

        return node

    def variable(self):
        """
        variable : ID
        """
        node = Var(self.current_token)
        self.eat(ID)

        return node


class NodeVisitor(object):
    def visit(self, node):
        method_name = 'visit_' + type(node).__name__
        visitor = getattr(self, method_name, self.generic_visit)
        return visitor(node)

    def generic_visit(self, node):
        raise Exception('No visit_{} method'.format(type(node).__name__))


class Interpreter(NodeVisitor):
    def __init__(self, parser):
        self.parser = parser
        self.GLOBAL_SCOPE = {}

    def error(self):
        raise Exception('Invalid syntax')

    def visit_Program(self, node):
        self.visit(node.block)

    def visit_Block(self, node):
        for declaration in node.declarations:
            self.visit(declaration)
        self.visit(node.compound_statement)

    def visit_VarDecl(self, node):
        # Do nothing
        pass

    def visit_Type(self, node):
        # Do nothing
        pass

    def visit_UnaryOp(self, node):
        if node.op.type == PLUS:
            return self.visit(node.expr)
        elif node.op.type == MINUS:
            return -1 * self.visit(node.expr)

    def visit_BinOp(self, node):
        if node.op.type == PLUS:
            return self.visit(node.left) + self.visit(node.right)
        elif node.op.type == MINUS:
            return self.visit(node.left) - self.visit(node.right)
        elif node.op.type == MUL:
            return self.visit(node.left) * self.visit(node.right)
        elif node.op.type == INTEGER_DIV:
            return int(self.visit(node.left) / self.visit(node.right))
        elif node.op.type == FLOAT_DIV:
            return float(self.visit(node.left) / self.visit(node.right))

    def visit_Num(self, node):
        return node.value

    def visit_Compound(self, node):
        for item in node.children:
            self.visit(item)

    def visit_NoOp(self, node):
        pass

    def visit_Assign(self, node):
        var_name = node.left.value
        self.GLOBAL_SCOPE[var_name] = self.visit(node.right)

    def visit_Var(self, node):
        var_name = node.value
        value = self.GLOBAL_SCOPE.get(var_name)
        if value is None:
            raise NameError(repr(var_name))

        return value

    def interpret(self):
        tree = self.parser.parse()
        return self.visit(tree)


def main():
    import sys
    text = open(sys.argv[1]).read()
    lexer = Lexer(text)
    parser = Parser(lexer)
    interpreter = Interpreter(parser)
    result = interpreter.interpret()
    print(result)


if __name__ == '__main__':
    main()
